package codeanalysis

import (
	"encoding/json"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
)

type Config struct {
	CodeDir        string
	GopathDir      string
	ProjectName    string
	PackageRep     map[string]string
	PackageRepSort []string
	VendorDir      string
	IgnoreDirs     []string
	IgnoreDirName  []string
	OutputFile     string
	SvgJar         string
}

type AnalysisResult interface {
	OutputToFile(logfile string)
}

func AnalysisCode(config Config) AnalysisResult {
	tool := &analysisTool{
		interfaceMetas:              []*interfaceMeta{},
		structMetas:                 []*structMeta{},
		typeAliasMetas:              []*typeAliasMeta{},
		packagePathPackageNameCache: map[string]string{},
		dependencyRelations:         []*DependencyRelation{},
		tempSuperTop:                map[string]string{},
		tempDownTop:                 map[string]bool{},
	}
	tool.analysis(config)
	return tool
}

func HasPrefixInSomeElement(value string, src []string) bool {
	result := false
	for _, srcValue := range src {
		if strings.HasPrefix(value, srcValue) {
			result = true
			break
		}
	}
	return result
}

func sliceContains(src []string, value string) bool {
	isContain := false
	for _, srcValue := range src {
		if srcValue == value {
			isContain = true
			break
		}
	}
	return isContain
}

func sliceContainsSlice(s []string, s2 []string) bool {
	for _, str := range s2 {
		if !sliceContains(s, str) {
			return false
		}
	}
	return true
}

// 将/ 反过来再对比一下
// s 结构体
// s2 接口
func sliceContainsSlice2(s []string, s2 []string) bool {
	for _, str := range s2 {
		if !sliceContains(s, str) {
			return false
		}
	}
	return true
}
func mapContains(src map[string]string, key string) bool {
	if _, ok := src[key]; ok {
		return true
	}
	return false
}

func findGoPackageNameInDirPath(dirpath string) string {

	dir_list, e := ioutil.ReadDir(dirpath)

	if e != nil {
		fmt.Errorf("读取目录%s文件列表失败,%s", dirpath, e)
		return ""
	}

	for _, fileInfo := range dir_list {
		if !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(), ".go") {
			packageName := ParsePackageNameFromGoFile(filepath.Join(dirpath, fileInfo.Name()))
			if packageName != "" {
				return packageName
			}
		}
	}

	return ""
}

func ParsePackageNameFromGoFile(filepath string) string {

	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, filepath, nil, parser.ParseComments)

	if err != nil {
		log.Errorf("解析文件%s失败, %s", filepath, err)
		return ""
	}

	return file.Name.Name

}

func PathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func packagePathToUML(packagePath string) string {
	packagePath = strings.Replace(packagePath, "/", "\\\\", -1)
	packagePath = strings.Replace(packagePath, "-", "_", -1)
	return packagePath
}

type baseInfo struct {
	// go文件路径
	FilePath string
	// 包路径, 例如 git.oschina.net/jscode/list-interface
	PackagePath string
}

type interfaceMeta struct {
	baseInfo
	Name string
	// interface的方法签名列表,
	MethodSigns []string
	Extends     []string // 继承的接口
	// UML图节点
	UML string
}

func (this *interfaceMeta) UniqueNameUML(pgn string) string {
	return strings.TrimPrefix(packagePathToUML(this.PackagePath), "\\"+pgn+"\\") + "." + this.Name
}
func (this *interfaceMeta) extendInterfaceUML(interfaceMeta1 *interfaceMeta, pgn string, extend string) string {
	return fmt.Sprintf("%s <|-- %s\n", extend, interfaceMeta1.UniqueNameUML(pgn))
}

type structMeta struct {
	baseInfo
	Name string
	// struct的方法签名列表
	MethodSigns []string
	// UML图节点
	UML string
}

type typeAliasMeta struct {
	baseInfo
	Name           string
	targetTypeName string
}

func (this *structMeta) UniqueNameUML(pgn string) string {
	s := strings.TrimPrefix(packagePathToUML(this.PackagePath), "\\"+pgn+"\\") + "." + this.Name
	return s
}

func (this *structMeta) implInterfaceUML(interfaceMeta1 *interfaceMeta, pgn string) string {
	return fmt.Sprintf("%s <|.. %s\n", interfaceMeta1.UniqueNameUML(pgn), this.UniqueNameUML(pgn))
}

type importMeta struct {
	// 例如 main
	Alias string
	// 例如 git.oschina.net/jscode/list-interface
	Path string
}

type DependencyRelation struct {
	source *structMeta
	target *structMeta
	uml    string
}

type analysisTool struct {
	config Config

	// 当前解析的go文件, 例如/appdev/go-demo/src/git.oschina.net/jscode/list-interface/a.go
	currentFile string
	// 当前解析的go文件,所在包路径, 例如git.oschina.net/jscode/list-interface
	currentPackagePath string
	// 当前解析的go文件,引入的其他包
	currentFileImports []*importMeta

	// 所有的interface
	interfaceMetas []*interfaceMeta
	// 所有的struct
	structMetas []*structMeta
	// 所有的别名定义
	typeAliasMetas []*typeAliasMeta
	// package path与package name的映射关系,例如git.oschina.net/jscode/list-interface 对应的pakcage name为 main
	packagePathPackageNameCache map[string]string
	// struct之间的依赖关系
	dependencyRelations []*DependencyRelation
	// struct中类似继承实现的接口
	infaceDependRelations []string
	// 临时的顶级top
	tempSuperTop map[string]string
	// 临时下级top
	tempDownTop map[string]bool
}

func (this *analysisTool) analysis(config Config) {

	this.config = config

	if this.config.CodeDir == "" || !PathExists(this.config.CodeDir) {
		log.Errorf("找不到代码目录%s\n", this.config.CodeDir)
		return
	}

	if this.config.GopathDir == "" || !PathExists(this.config.GopathDir) {
		log.Errorf("找不到GOPATH目录%s\n", this.config.GopathDir)
		return
	}

	for _, lib := range stdlibs {
		this.mapPackagePath_PackageName(lib, filepath.Base(lib))
	}
	dir_walk_once := func(path string, info os.FileInfo, err error) error {
		// 过滤掉测试代码
		if strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "test.go") {
			if config.IgnoreDirs != nil && HasPrefixInSomeElement(path, config.IgnoreDirs) {
				// ignore
			} else if config.IgnoreDirName != nil && IsInDirName(path, config.IgnoreDirName) {
				// ignore
			} else {
				log.Info("解析 " + path)
				this.visitTypeInFile(path)
			}
		}

		return nil
	}

	filepath.Walk(config.CodeDir, dir_walk_once)

	dir_walk_twice := func(path string, info os.FileInfo, err error) error {
		// 过滤掉测试代码
		if strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "test.go") {
			if config.IgnoreDirs != nil && HasPrefixInSomeElement(path, config.IgnoreDirs) {
				// ignore
			} else if config.IgnoreDirName != nil && IsInDirName(path, config.IgnoreDirName) {
				// ignore
			} else {
				log.Info("解析 " + path)
				this.visitFuncInFile(path)
			}
		}

		return nil
	}

	filepath.Walk(config.CodeDir, dir_walk_twice)

}

func (this *analysisTool) initFile(path string) {
	log.Debug("path=", path)

	this.currentFile = path
	this.currentPackagePath = this.filepathToPackagePath(path)

	if this.currentPackagePath == "" {
		log.Errorf("packagePath为空,currentFile=%s\n", this.currentFile)
	}

}
func IsInDirName(dirName string, filter []string) bool {
	for _, s := range filter {
		if s == "" {
			continue
		}
		if strings.Contains(dirName, s) {
			return true
		}
	}
	return false
}
func (this *analysisTool) mapPackagePath_PackageName(packagePath string, packageName string) {
	if packagePath == "" || packageName == "" {
		log.Errorf("mapPackagePath_PackageName, packageName=%s, packagePath=%s\n, current_file=%s",
			packageName, packagePath, this.currentFile)
		return
	}

	if mapContains(this.packagePathPackageNameCache, packagePath) {
		return
	}

	log.Debugf("mapPackagePath_PackageName, packageName=%s, packagePath=%s\n", packageName, packagePath)
	this.packagePathPackageNameCache[packagePath] = packageName

}

func (this *analysisTool) visitTypeInFile(path string) {

	this.initFile(path)

	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)

	if err != nil {
		log.Fatal(err)
		return
	}

	this.mapPackagePath_PackageName(this.currentPackagePath, file.Name.Name)

	for _, decl := range file.Decls {

		genDecl, ok := decl.(*ast.GenDecl)

		if ok {
			for _, spec := range genDecl.Specs {

				typeSpec, ok := spec.(*ast.TypeSpec)

				if ok {
					this.visitTypeSpec(typeSpec)
				}
			}
		}

	}

}

func (this *analysisTool) visitTypeSpec(typeSpec *ast.TypeSpec) {

	interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
	if ok {
		this.visitInterfaceType(typeSpec.Name.Name, interfaceType)
		return
	}

	structType, ok := typeSpec.Type.(*ast.StructType)
	if ok {
		this.visitStructType(typeSpec.Name.Name, structType)
		return
	}

	// 其他类型别名
	this.typeAliasMetas = append(this.typeAliasMetas, &typeAliasMeta{
		baseInfo: baseInfo{
			FilePath:    this.currentFile,
			PackagePath: this.currentPackagePath,
		},
		Name:           typeSpec.Name.Name,
		targetTypeName: "",
	})

}

func (this *analysisTool) filepathToPackagePath(filepath2 string) string {

	filepath2 = filepath.Dir(filepath2)

	if this.config.VendorDir != "" {
		if strings.HasPrefix(filepath2, this.config.VendorDir) {
			packagePath := strings.TrimPrefix(filepath2, this.config.VendorDir)
			packagePath = strings.TrimPrefix(packagePath, "/")
			return packagePath
		}
	}

	if this.config.GopathDir != "" {
		srcdir := filepath.Join(this.config.GopathDir, "src")
		if strings.HasPrefix(filepath2, srcdir) {
			packagePath := strings.TrimPrefix(filepath2, srcdir)
			packagePath = strings.TrimPrefix(packagePath, "/")
			return packagePath
		}
	}

	log.Errorf("无法确认包路径名, filepath=%s\n", filepath2)

	return ""

}

func (this *analysisTool) visitFuncInFile(path string) {

	this.initFile(path)

	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)

	if err != nil {
		log.Fatal(err)
		return
	}

	this.currentFileImports = []*importMeta{}

	if file.Imports != nil {
		for _, import1 := range file.Imports {

			alias := ""
			packagePath := strings.TrimSuffix(strings.TrimPrefix(import1.Path.Value, "\""), "\"")
			if import1.Name != nil {
				alias = import1.Name.Name
			} else {
				aliasCache, ok := this.packagePathPackageNameCache[packagePath]
				log.Debugf("findAliasInCache,packagePath=%s,alias=%s,ok=%t\n", packagePath, aliasCache, ok)
				if ok {
					alias = aliasCache
				} else {
					alias = this.findAliasByPackagePath(packagePath)
				}
			}

			log.Debugf("current_file=%s packagePath=%s, alias=%s\n", this.currentFile, packagePath, alias)
			this.currentFileImports = append(this.currentFileImports, &importMeta{
				Alias: alias,
				Path:  packagePath,
			})
		}
	}

	for _, decl := range file.Decls {

		genDecl, ok := decl.(*ast.GenDecl)

		if ok {
			for _, spec := range genDecl.Specs {

				typeSpec, ok := spec.(*ast.TypeSpec)

				if ok {

					interfaceType, ok := typeSpec.Type.(*ast.InterfaceType)
					if ok {
						this.visitInterfaceFunctions(typeSpec.Name.Name, interfaceType)
					}

					structType, ok := typeSpec.Type.(*ast.StructType)
					if ok {
						this.visitStructFields(typeSpec.Name.Name, structType)
					}

				}
			}
		}

	}

	for _, decl := range file.Decls {

		funcDecl, ok := decl.(*ast.FuncDecl)
		if ok {
			this.visitFunc(funcDecl)
		}

	}

}

func (this *analysisTool) visitStructType(name string, structType *ast.StructType) {

	strutMeta1 := &structMeta{
		baseInfo: baseInfo{
			FilePath:    this.currentFile,
			PackagePath: this.currentPackagePath,
		},
		Name:        name,
		MethodSigns: []string{},
	}

	this.structMetas = append(this.structMetas, strutMeta1)

}

func (this *analysisTool) visitStructFields(structName string, structType *ast.StructType) {

	sourceStruct1 := this.findStruct(this.currentPackagePath, structName)

	sourceStruct1.UML = this.structToUML(structName, structType)

	for _, field := range structType.Fields.List {
		this.visitStructField(sourceStruct1, field)
	}

}

func (this *analysisTool) visitStructField(sourceStruct1 *structMeta, field *ast.Field) {

	fieldNames := this.IdentsToString(field.Names)

	targetStruct1, isarray := this.analysisTypeForDependencyRelation(field.Type)

	if targetStruct1 != nil {

		if fieldNames == "" {
			// 继承
			d := DependencyRelation{
				source: sourceStruct1,
				target: targetStruct1,
				uml:    sourceStruct1.UniqueNameUML(this.config.ProjectName) + " -||> " + targetStruct1.UniqueNameUML(this.config.ProjectName),
			}

			this.dependencyRelations = append(this.dependencyRelations, &d)

		} else {

			if isarray {
				// 数组
				d := DependencyRelation{
					source: sourceStruct1,
					target: targetStruct1,
					uml:    sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> \"*\" " + targetStruct1.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames,
				}

				this.dependencyRelations = append(this.dependencyRelations, &d)

			} else {
				d := DependencyRelation{
					source: sourceStruct1,
					target: targetStruct1,
					uml:    sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> " + targetStruct1.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames,
				}

				this.dependencyRelations = append(this.dependencyRelations, &d)

			}

		}

	} else {
		var t ast.Expr = field.Type
		_, ok := t.(*ast.InterfaceType) // 是接口
		if ok {
			name := field.Names[0].Name
			if this.isGoBaseType(name) {
				return
			}

			packagepath := this.findPackagePathByAlias("", name)

			if packagepath == "" {
				return
			}
			inf := this.findInterfaceMeta(packagepath, name)
			if inf == nil {
				return
			}
			if fieldNames == "" {
				// 继承实现 接口
				d := inf.UniqueNameUML(this.config.ProjectName) + " <|... " + sourceStruct1.UniqueNameUML(this.config.ProjectName) + "\n"
				this.infaceDependRelations = append(this.infaceDependRelations, d)

			} else {

				if isarray {
					// 数组
					d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> \"*\" " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

					this.infaceDependRelations = append(this.infaceDependRelations, d)

				} else {
					d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

					this.infaceDependRelations = append(this.infaceDependRelations, d)

				}

			}
		} else {

			arrayType, ok := field.Type.(*ast.ArrayType)
			if ok {
				t = arrayType.Elt
			}

			mapType, ok := field.Type.(*ast.MapType)
			if ok {
				t = mapType.Value
			}
			selectors, ok := field.Type.(*ast.SelectorExpr)
			if ok {
				t = selectors
			}
			iden, ok := t.(*ast.Ident)
			if ok {
				if iden.Obj == nil || iden.Obj.Decl == nil {
					return
				}
				if dc, ok := iden.Obj.Decl.(*ast.TypeSpec); ok {
					_, ok := dc.Type.(*ast.InterfaceType) // 是接口
					if ok {
						name := iden.Name
						if this.isGoBaseType(name) {
							return
						}

						packagepath := this.findPackagePathByAlias("", name)

						if packagepath == "" {
							return
						}
						inf := this.findInterfaceMeta(packagepath, name)
						if inf == nil {
							return
						}
						if fieldNames == "" {
							// 继承实现 接口
							d := inf.UniqueNameUML(this.config.ProjectName) + " <|... " + sourceStruct1.UniqueNameUML(this.config.ProjectName) + "\n"
							this.infaceDependRelations = append(this.infaceDependRelations, d)

						} else {

							if isarray {
								// 数组
								d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> \"*\" " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

								this.infaceDependRelations = append(this.infaceDependRelations, d)

							} else {
								d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

								this.infaceDependRelations = append(this.infaceDependRelations, d)

							}

						}
					}
				}
			} else if selectors != nil {
				// 直接评定为接口吧
				name := selectors.Sel.Name
				if this.isGoBaseType(name) {
					return
				}
				alice := ""
				ali, ok := selectors.X.(*ast.Ident)
				if ok {
					alice = ali.Name
				}
				packagepath := this.findPackagePathByAlias(alice, name)

				if packagepath == "" {
					return
				}
				inf := this.findInterfaceMeta(packagepath, name)
				if inf == nil {
					return
				}
				if fieldNames == "" {
					// 继承实现 接口
					d := inf.UniqueNameUML(this.config.ProjectName) + " <|... " + sourceStruct1.UniqueNameUML(this.config.ProjectName) + "\n"
					this.infaceDependRelations = append(this.infaceDependRelations, d)

				} else {

					if isarray {
						// 数组
						d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> \"*\" " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

						this.infaceDependRelations = append(this.infaceDependRelations, d)

					} else {
						d := sourceStruct1.UniqueNameUML(this.config.ProjectName) + " ---> " + inf.UniqueNameUML(this.config.ProjectName) + " : " + fieldNames + "\n"

						this.infaceDependRelations = append(this.infaceDependRelations, d)

					}

				}
			}
		}
	}

}

func (this *analysisTool) isGoBaseType(type1 string) bool {

	baseTypes := []string{"bool", "byte", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64",
		"float32", "float64", "complex64", "complex128", "string", "uintptr", "rune", "error"}

	if sliceContains(baseTypes, type1) {
		return true
	}

	return false
}

func (this *analysisTool) findStructByAliasAndStructName(alias string, structName string) *structMeta {
	if alias == "" && this.isGoBaseType(structName) {
		return nil
	}

	packagepath := this.findPackagePathByAlias(alias, structName)

	if packagepath != "" {
		return this.findStruct(packagepath, structName)
	}

	return nil
}

func (this *analysisTool) analysisTypeForDependencyRelation(t ast.Expr) (structMeta1 *structMeta, isArray bool) {

	structMeta1 = nil
	isArray = false
	ident, ok := t.(*ast.Ident)
	if ok {
		structMeta1 = this.findStructByAliasAndStructName("", ident.Name)
		isArray = false
		return
	}

	starExpr, ok := t.(*ast.StarExpr)
	if ok {
		structMeta1, isArray = this.analysisTypeForDependencyRelation(starExpr.X)
		return
	}

	arrayType, ok := t.(*ast.ArrayType)
	if ok {
		eleStructName, _ := this.analysisTypeForDependencyRelation(arrayType.Elt)
		structMeta1 = eleStructName
		isArray = true
		return
	}

	mapType, ok := t.(*ast.MapType)
	if ok {
		valueStructMeta1, _ := this.analysisTypeForDependencyRelation(mapType.Value)
		structMeta1 = valueStructMeta1
		isArray = true
		return
	}

	selectorExpr, ok := t.(*ast.SelectorExpr)
	if ok {
		alias := this.typeToString(selectorExpr.X, false)
		structMeta1 = this.findStructByAliasAndStructName(alias, this.typeToString(selectorExpr.Sel, false))
		isArray = false
		return
	}

	return
}

func (this *analysisTool) structToUML(name string, structType *ast.StructType) string {
	classUML := "class " + name + " " + this.structBodyToString(structType)
	return fmt.Sprintf("namespace %s {\n %s \n}", strings.TrimPrefix(this.packagePathToUML(this.currentPackagePath), "\\"+this.config.ProjectName+"\\"), classUML)
}

func (this *analysisTool) packagePathToUML(packagePath string) string {
	return packagePathToUML(packagePath)
}

func (this *analysisTool) structBodyToString(structType *ast.StructType) string {

	result := "{\n"

	for _, field := range structType.Fields.List {
		result += "  " + this.fieldToString(field) + "\n"
	}

	result += "}"

	return result

}

func (this *analysisTool) visitInterfaceType(name string, interfaceType *ast.InterfaceType) {

	interfaceInfo1 := &interfaceMeta{
		baseInfo: baseInfo{
			FilePath:    this.currentFile,
			PackagePath: this.currentPackagePath,
		},
		Name: name,
	}

	this.interfaceMetas = append(this.interfaceMetas, interfaceInfo1)

}

func (this *analysisTool) interfaceToUML(name string, interfaceType *ast.InterfaceType) string {
	interfaceUML := "interface " + name + " " + this.interfaceBodyToString(interfaceType)
	return fmt.Sprintf("namespace %s {\n %s \n}", strings.TrimPrefix(this.packagePathToUML(this.currentPackagePath), "\\"+this.config.ProjectName+"\\"), interfaceUML)
}

func (this *analysisTool) funcParamsResultsToString(funcType *ast.FuncType) string {

	funcString := "("

	if funcType.Params != nil {
		for index, field := range funcType.Params.List {
			if index != 0 {
				funcString += ","
			}

			funcString += this.fieldToString(field)
		}
	}

	funcString += ")"

	if funcType.Results != nil {

		if len(funcType.Results.List) >= 2 {
			funcString += "("
		}

		for index, field := range funcType.Results.List {
			if index != 0 {
				funcString += ","
			}

			funcString += this.fieldToString(field)
		}

		if len(funcType.Results.List) >= 2 {
			funcString += ")"
		}
	}

	return funcString

}

func (this *analysisTool) findStruct(packagePath string, structName string) *structMeta {

	for _, structMeta1 := range this.structMetas {
		pt := strings.ReplaceAll(structMeta1.PackagePath, "\\", ".")
		pt = strings.ReplaceAll(pt, "/", ".")
		if pt[0] == '*' && pt[1] == '.' {
			pt = "*" + pt[2:]
		} else if pt[0] == '.' {
			pt = pt[1:]
		}
		pk := strings.ReplaceAll(packagePath, "\\", ".")
		pk = strings.ReplaceAll(pk, "/", ".")
		if pk[0] == '*' && pk[1] == '.' {
			pk = "*" + pk[2:]
		} else if pk[0] == '.' {
			pk = pk[1:]
		}
		for _, s := range this.config.PackageRepSort {
			if strings.Contains(pk, s) {
				pk = strings.Replace(pk, s, this.config.PackageRep[s], 1)
				break
			}
		}
		if structMeta1.Name == structName && pt == pk {
			return structMeta1
		}
	}

	return nil
}

func (this *analysisTool) findTypeAlias(packagePath string, structName string) *typeAliasMeta {

	for _, typeAliasMeta1 := range this.typeAliasMetas {
		if typeAliasMeta1.Name == structName && typeAliasMeta1.PackagePath == packagePath {
			return typeAliasMeta1
		}
	}

	return nil
}

func (this *analysisTool) findInterfaceMeta(packagePath string, interfaceName string) *interfaceMeta {

	for _, interfaceMeta := range this.interfaceMetas {
		pt := strings.ReplaceAll(interfaceMeta.PackagePath, "\\", ".")
		pt = strings.ReplaceAll(pt, "/", ".")
		if pt[0] == '*' && pt[1] == '.' {
			pt = "*" + pt[2:]
		} else if pt[0] == '.' {
			pt = pt[1:]
		}
		pk := strings.ReplaceAll(packagePath, "\\", ".")
		pk = strings.ReplaceAll(pk, "/", ".")
		if pk[0] == '*' && pk[1] == '.' {
			pk = "*" + pk[2:]
		} else if pk[0] == '.' {
			pk = pk[1:]
		}
		for _, s := range this.config.PackageRepSort {
			if strings.Contains(pt, s) {
				pt = strings.Replace(pt, s, this.config.PackageRep[s], 1)
				break
			}
		}
		if interfaceMeta.Name == interfaceName && pt == pk {
			return interfaceMeta
		}
	}

	return nil
}

func (this *analysisTool) visitFunc(funcDecl *ast.FuncDecl) {

	this.debugFunc(funcDecl)

	packageAlias, structName := this.findStructTypeOfFunc(funcDecl)

	if structName != "" {

		packagePath := ""
		if packageAlias == "" {
			packagePath = this.currentPackagePath
		}

		structMeta := this.findStruct(packagePath, structName)
		if structMeta != nil {
			methodSign := this.createMethodSign(funcDecl.Name.Name, funcDecl.Type)
			structMeta.MethodSigns = append(structMeta.MethodSigns, methodSign)
		}
	}

}

func (this *analysisTool) visitInterfaceFunctions(name string, interfaceType *ast.InterfaceType) {

	methods := []string{}
	extends := make([]string, 0)
	for _, field := range interfaceType.Methods.List {

		funcType, ok := field.Type.(*ast.FuncType)

		if ok {
			methods = append(methods, this.createMethodSign(field.Names[0].Name, funcType))
		} else {
			infac, ok := field.Type.(*ast.SelectorExpr)
			if ok {
				pa := ""
				for _, currentFileImport := range this.currentFileImports {
					if currentFileImport.Alias == infac.X.(*ast.Ident).Name {
						pa = currentFileImport.Path
					}
				}
				ext := this.findInterfaceMeta(pa, infac.Sel.Name)
				if ext == nil {
					continue
				}
				extends = append(extends, ext.UniqueNameUML(this.config.ProjectName)) // 接口继承的接口
			}
		}
	}

	interfaceMeta := this.findInterfaceMeta(this.currentPackagePath, name)
	interfaceMeta.MethodSigns = methods
	interfaceMeta.Extends = extends
	interfaceMeta.UML = this.interfaceToUML(name, interfaceType)

}

func (this *analysisTool) findStructTypeOfFunc(funcDecl *ast.FuncDecl) (packageAlias string, structName string) {

	if funcDecl.Recv != nil {

		for _, field := range funcDecl.Recv.List {

			t := field.Type

			ident, ok := t.(*ast.Ident)
			if ok {
				packageAlias = ""
				structName = ident.Name
			}

			starExpr, ok := t.(*ast.StarExpr)
			if ok {
				ident, ok := starExpr.X.(*ast.Ident)
				if ok {
					packageAlias = ""
					structName = ident.Name
				}

			}
		}
	}

	return
}

func (this *analysisTool) debugFunc(funcDecl *ast.FuncDecl) {

	log.Debug("func name=", funcDecl.Name)

	if funcDecl.Recv != nil {
		for _, field := range funcDecl.Recv.List {
			log.Debug("func recv, name=", field.Names, " type=", field.Type)
		}
	}

	if funcDecl.Type.Params != nil {
		for _, field := range funcDecl.Type.Params.List {
			log.Debug("func param, name=", field.Names, " type=", field.Type)
		}
	}

	if funcDecl.Type.Results != nil {
		for _, field := range funcDecl.Type.Results.List {
			log.Debug("func result, type=", field.Type)
		}
	}

}

func (this *analysisTool) IdentsToString(names []*ast.Ident) string {
	r := ""
	for index, name := range names {
		if index != 0 {
			r += ","
		}
		r += name.Name
	}
	if r == "" {
		return ""
	}
	if (r[0] > 'a' && r[0] < 'z') || r[0] == '_' {
		r = "- " + r
	} else {
		r = "+ " + r
	}
	return r
}

// 创建方法签名
func (this *analysisTool) createMethodSign(methodName string, funcType *ast.FuncType) string {

	methodSign := methodName + "("

	if funcType.Params != nil {
		for index, field := range funcType.Params.List {
			if index != 0 {
				methodSign += ","
			}
			methodSign += this.fieldToStringInMethodSign(field)
		}
	}

	methodSign += ")"

	if funcType.Results != nil {

		if len(funcType.Results.List) >= 2 {
			methodSign += "("
		}

		for index, field := range funcType.Results.List {
			if index != 0 {
				methodSign += ","
			}
			methodSign += this.fieldToStringInMethodSign(field)
		}

		if len(funcType.Results.List) >= 2 {
			methodSign += ")"
		}
	}

	return methodSign
}

func (this *analysisTool) fieldToStringInMethodSign(f *ast.Field) string {

	argCount := len(f.Names)

	if argCount == 0 {
		argCount = 1
	}

	sign := ""

	for i := 0; i < argCount; i++ {
		if i != 0 {
			sign += ","
		}
		sign += this.typeToString(f.Type, true)
	}

	return sign
}

func (this *analysisTool) fieldToString(f *ast.Field) string {

	r := ""

	if len(f.Names) > 0 {

		for index, name := range f.Names {
			if index != 0 {
				r += ","
			}

			r += name.Name
		}

		r += " "

	}

	r += this.typeToString(f.Type, false)
	r = strings.TrimSpace(r)
	if r[0] >= 'a' && r[0] <= 'z' {
		r = "- " + r
	} else {
		r = "+ " + r
	}
	return r

}

func (this *analysisTool) typeToString(t ast.Expr, convertTypeToUnqiueType bool) string {

	ident, ok := t.(*ast.Ident)
	if ok {
		if convertTypeToUnqiueType {
			return this.addPackagePathWhenStruct(ident.Name)
		} else {
			return ident.Name
		}
	}

	starExpr, ok := t.(*ast.StarExpr)
	if ok {
		return "*" + this.typeToString(starExpr.X, convertTypeToUnqiueType)
	}

	arrayType, ok := t.(*ast.ArrayType)
	if ok {
		return "[]" + this.typeToString(arrayType.Elt, convertTypeToUnqiueType)
	}

	mapType, ok := t.(*ast.MapType)
	if ok {
		return "map[" + this.typeToString(mapType.Key, convertTypeToUnqiueType) + "]" + this.typeToString(mapType.Value, convertTypeToUnqiueType)
	}

	chanType, ok := t.(*ast.ChanType)
	if ok {
		return "chan " + this.typeToString(chanType.Value, convertTypeToUnqiueType)
	}

	funcType, ok := t.(*ast.FuncType)
	if ok {
		return "func" + this.funcParamsResultsToString(funcType)
	}

	interfaceType, ok := t.(*ast.InterfaceType)
	if ok {
		return "interface " + strings.Replace(this.interfaceBodyToString(interfaceType), "\n", " ", -1)
	}

	selectorExpr, ok := t.(*ast.SelectorExpr)
	if ok {
		if convertTypeToUnqiueType {
			return this.findPackagePathByAlias(this.selectorExprToString(selectorExpr.X), selectorExpr.Sel.Name) + "." + selectorExpr.Sel.Name
		} else {
			return this.typeToString(selectorExpr.X, true) + "." + selectorExpr.Sel.Name
		}
	}

	structType, ok := t.(*ast.StructType)
	if ok {
		return "struct " + strings.Replace(this.structBodyToString(structType), "\n", " ", -1)
	}

	ellipsis, ok := t.(*ast.Ellipsis)
	if ok {
		return "... " + this.typeToString(ellipsis.Elt, convertTypeToUnqiueType)
	}

	parenExpr, ok := t.(*ast.ParenExpr)
	if ok {
		return " (" + this.typeToString(parenExpr.X, convertTypeToUnqiueType) + ")"
	}

	log.Error("typeToString ", reflect.TypeOf(t), " file=", this.currentFile, " expr=", this.content(t))

	return ""
}

func (this *analysisTool) selectorExprToString(t ast.Expr) string {

	ident, ok := t.(*ast.Ident)
	if ok {
		return ident.Name
	}

	log.Error("selectorExprToString ", reflect.TypeOf(t), " file=", this.currentFile, " expr=", this.content(t))

	return ""
}

func (this *analysisTool) addPackagePathWhenStruct(fieldType string) (ret string) {
	defer func() {
		// 和struct中的统一路径结构
		if strings.HasPrefix(ret, "\\") {
			ret = ret[1:]
		} else if strings.HasPrefix(ret, "*\\") {
			ret = "*" + ret[2:]
		}
		ret = strings.ReplaceAll(ret, "\\", "/")
	}()
	searchPackages := []string{this.currentPackagePath}

	for _, import1 := range this.currentFileImports {
		if import1.Alias == "." {
			searchPackages = append(searchPackages, import1.Path)
		}
	}

	for _, meta := range this.structMetas {
		if sliceContains(searchPackages, meta.PackagePath) && meta.Name == fieldType {
			return meta.PackagePath + "." + fieldType
		}
	}

	for _, meta := range this.interfaceMetas {
		if sliceContains(searchPackages, meta.PackagePath) && meta.Name == fieldType {
			return meta.PackagePath + "." + fieldType
		}
	}
	for _, meta := range this.typeAliasMetas {
		if sliceContains(searchPackages, meta.PackagePath) && meta.Name == fieldType {
			return meta.PackagePath + "." + fieldType
		}
	}
	return fieldType
}

func (this *analysisTool) findAliasByPackagePath(packagePath string) string {
	result := ""

	if this.config.VendorDir != "" {
		absPath := filepath.Join(this.config.VendorDir, packagePath)
		if PathExists(absPath) {
			result = findGoPackageNameInDirPath(absPath)
		}
	}

	if this.config.GopathDir != "" {
		absPath := filepath.Join(this.config.GopathDir, "src", packagePath)
		if PathExists(absPath) {
			result = findGoPackageNameInDirPath(absPath)
		}
	}

	log.Debugf("packagepath=%s, alias=%s\n", packagePath, result)

	return result
}

func (this *analysisTool) existStructOrInterfaceInPackage(typeName string, packageName string) bool {
	structMeta1 := this.findStruct(this.currentPackagePath, typeName)
	if structMeta1 != nil {
		return true
	}

	interfaceMeta1 := this.findInterfaceMeta(this.currentPackagePath, typeName)
	if interfaceMeta1 != nil {
		return true
	}

	return false
}

func (this *analysisTool) existTypeAliasInPackage(typeName string, packageName string) bool {
	meta1 := this.findTypeAlias(this.currentPackagePath, typeName)
	if meta1 != nil {
		return true
	}

	return false
}

func (this *analysisTool) findPackagePathByAlias(alias string, structName string) string {

	if alias == "" {

		if this.existStructOrInterfaceInPackage(structName, this.currentPackagePath) {
			return this.currentPackagePath
		}

		if this.existTypeAliasInPackage(structName, this.currentPackagePath) {
			// 忽略别名类型
			return ""
		}

		matchedImportMetas := []*importMeta{}

		for _, importMeta := range this.currentFileImports {
			if importMeta.Alias == "." {
				matchedImportMetas = append(matchedImportMetas, importMeta)
			}
		}

		if len(matchedImportMetas) > 1 {

			for _, matchedImportMeta := range matchedImportMetas {

				if this.existStructOrInterfaceInPackage(structName, matchedImportMeta.Path) {
					log.Debugf("findPackagePathByAlias, alias=%s, packagePath=%s\n", alias, matchedImportMeta.Path)
					return matchedImportMeta.Path
				}

				if this.existTypeAliasInPackage(structName, matchedImportMeta.Path) {
					// 忽略别名类型
					return ""
				}

			}

		}

		currentFileImportsjson, _ := json.Marshal(this.currentFileImports)
		log.Warnf("找不到包的全路径，包名为%s，type name=%s, 在%s文件, matchedImportMetas=%d, currentFileImports=%s", alias, structName, this.currentFile, len(matchedImportMetas), currentFileImportsjson)

		return alias

	} else {

		for _, importMeta := range this.currentFileImports {
			if importMeta.Path == alias {
				log.Debugf("findPackagePathByAlias, alias=%s, packagePath=%s\n", alias, alias)
				return alias
			}
		}

		matchedImportMetas := []*importMeta{}

		for _, importMeta := range this.currentFileImports {
			if importMeta.Alias == "" { // 将没有包别名的，取个别名，"github.com/jessevdk/go-flags" 变为了 go-flags "github.com/jessevdk/go-flags"
				pk := strings.Split(importMeta.Path, "/")
				importMeta.Alias = pk[len(pk)-1]
			}
			if importMeta.Alias == alias {
				matchedImportMetas = append(matchedImportMetas, importMeta)
			}
		}

		if len(matchedImportMetas) == 1 {
			log.Debugf("findPackagePathByAlias, alias=%s, packagePath=%s\n", alias, matchedImportMetas[0].Path)
			return matchedImportMetas[0].Path
		}

		if len(matchedImportMetas) > 1 {

			for _, matchedImportMeta := range matchedImportMetas {

				if this.existStructOrInterfaceInPackage(structName, matchedImportMeta.Path) {
					log.Debugf("findPackagePathByAlias, alias=%s, packagePath=%s\n", alias, matchedImportMeta.Path)
					return matchedImportMeta.Path
				}

				if this.existTypeAliasInPackage(structName, matchedImportMeta.Path) {
					// 忽略别名类型
					return ""
				}

			}

		}

		currentFileImportsjson, _ := json.Marshal(this.currentFileImports)
		log.Warnf("找不到包的全路径，包名为%s，type name=%s, 在%s文件, matchedImportMetas=%d, currentFileImports=%s", alias, structName, this.currentFile, len(matchedImportMetas), currentFileImportsjson)

		return alias

	}

}

func (this *analysisTool) interfaceBodyToString(interfaceType *ast.InterfaceType) string {

	result := " {\n"

	for _, field := range interfaceType.Methods.List {

		funcType, ok := field.Type.(*ast.FuncType)

		if ok {
			result += "  " + this.IdentsToString(field.Names) + this.funcParamsResultsToString(funcType) + "\n"
		}

	}

	result += "}"

	return result

}

func (this *analysisTool) content(t ast.Expr) string {
	bytes, err := ioutil.ReadFile(this.currentFile)
	if err != nil {
		log.Error("读取文件", this.currentFile, "失败", err)
		return ""
	}

	return string(bytes[t.Pos()-1 : t.End()-1])
}

/**
 * 查找interface有哪些实现的Struct
 */
func (this *analysisTool) findInterfaceImpls(interfaceMeta1 *interfaceMeta) []*structMeta {
	metas := []*structMeta{}

	for _, structMeta1 := range this.structMetas {
		if sliceContainsSlice(structMeta1.MethodSigns, interfaceMeta1.MethodSigns) {
			metas = append(metas, structMeta1)
		}
	}

	return metas
}
func (this *analysisTool) buildRoot() string {
	str := "namespace %s {}"
	uml := ""
	sd := map[string]bool{}
	for _, structMeta1 := range this.structMetas {
		if strings.HasPrefix(structMeta1.UML, "namespace") {
			ns := getNS(structMeta1.UML)
			if ns == "" {
				continue
			}
			na := strings.Split(strings.TrimSpace(ns), "\\")
			for i := 1; i < len(na); i++ {
				s := strings.Builder{}
				for j := 0; j < i; j++ {
					s.WriteString(na[j])
					if j < i-1 {
						s.WriteByte('.')
					}
				}
				if s.Len() == 0 {
					continue
				}
				sd[fmt.Sprintf(str, s.String())+"\n"] = true
				//this.tempSuperTop[s.String()] = fmt.Sprintf(str,s.String())+"\n"
			}
		}
	}

	for _, interfaceMeta1 := range this.interfaceMetas {
		if strings.HasPrefix(interfaceMeta1.UML, "namespace") {
			ns := getNS(interfaceMeta1.UML)
			if ns == "" {
				continue
			}
			na := strings.Split(strings.TrimSpace(ns), "\\")
			for i := 1; i < len(na); i++ {
				s := strings.Builder{}
				for j := 0; j < i; j++ {
					s.WriteString(na[j])
					if j < i-1 {
						s.WriteByte('.')
					}
				}
				if s.Len() == 0 {
					continue
				}
				sd[fmt.Sprintf(str, s.String())+"\n"] = true
				//this.tempSuperTop[s.String()] = fmt.Sprintf(str,s.String())+"\n"
			}
		}
	}
	for _, s := range getArr(sd) {
		uml += s
	}
	return uml
}
func getArr(sd map[string]bool) []string {
	arr := make(Arr, 0)
	for s, _ := range sd {
		arr = append(arr, s)
	}
	sort.Sort(arr)
	return arr
}

type Arr []string

func (a Arr) Len() int           { return len(a) }
func (a Arr) Less(i, j int) bool { return len(a[i]) < len(a[j]) }
func (a Arr) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func getNS(uml string) string {
	ns := strings.Split(strings.TrimPrefix(uml, "namespace"), "{")
	if len(ns) == 0 {
		return ""
	}
	return ns[0]
}
func (this *analysisTool) UML() string {

	uml := this.buildRoot()

	for _, structMeta1 := range this.structMetas {
		ns := getNS(structMeta1.UML)
		if ns != "" {
			this.tempDownTop[ns] = true
		}
		uml += structMeta1.UML
		uml += "\n"
	}

	for _, interfaceMeta1 := range this.interfaceMetas {
		ns := getNS(interfaceMeta1.UML)
		if ns != "" {
			this.tempDownTop[ns] = true
		}
		uml += interfaceMeta1.UML
		uml += "\n"
	}

	for _, d := range this.dependencyRelations {
		uml += d.uml
		uml += "\n"
	}

	for _, interfaceMeta1 := range this.interfaceMetas {
		structMetas := this.findInterfaceImpls(interfaceMeta1)
		for _, structMeta := range structMetas {
			uml += structMeta.implInterfaceUML(interfaceMeta1, this.config.ProjectName)
		}
		// 该接口继承的接口
		for _, extend := range interfaceMeta1.Extends {
			uml += interfaceMeta1.extendInterfaceUML(interfaceMeta1, this.config.ProjectName, extend)
		}
	}

	//for k, _ := range this.tempDownTop {
	//	add(strings.Split(k,"\\"))
	//}
	//top := getTop()
	//str := "namespace %s {}\n"
	//tempUml := ""
	//for i := 0; i < len(top); i++ {
	//	tempUml += fmt.Sprintf(str,strings.TrimSpace(top[i]))
	//}
	//uml = tempUml+uml
	for _, relation := range this.infaceDependRelations {
		uml += relation
	}
	return "@startuml\n" + uml + "@enduml"
}

var r = root{next: map[string]*root{}}

type root struct {
	val  string
	next map[string]*root
}

func (r *root) add(v []string) {
	if len(v) == 0 {
		return
	}
	var p *root
	var ok bool
	if p, ok = r.next[v[0]]; !ok {
		p = &root{next: map[string]*root{}, val: v[0]}
		r.next[v[0]] = p
	}
	if len(v) == 1 {
		return
	}
	p.add(v[1:])
}
func add(node []string) {
	if len(node) <= 1 {
		return
	}
	for _, s := range node {
		var p *root
		var ok bool
		if p, ok = r.next[s]; !ok {
			p = &root{next: map[string]*root{}, val: s}
			r.next[s] = p
		}
		if len(node) == 1 {
			continue
		}
		p.add(node[1:])
	}
}
func getTop() []string {
	ret := make([]string, 0)
	for _, node := range r.next {
		ret = append(ret, getNext("", node))
	}
	return ret
}
func getNext(s string, rt *root) string {
	s += strings.TrimSpace(rt.val)
	if len(rt.next) > 1 {
		return s
	} else if len(rt.next) == 1 {
		var node *root
		for _, node = range rt.next {
		}
		s += "\\"
		return getNext(s, node)
	} else {
		return s
	}
}

func (this *analysisTool) OutputToFile(logfile string) {

	uml := this.UML()
	// 防止转义字符，需要用两个\\
	ioutil.WriteFile(logfile, []byte(strings.ReplaceAll(uml, "\\", ".")), 0666)
	log.Infof("数据已保存到%s\n", logfile)

}
